/*
    Copyright (c) 2009, Salesforce.com Foundation
    All rights reserved.
    
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
    
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Salesforce.com Foundation nor the names of
      its contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.
 
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
    POSSIBILITY OF SUCH DAMAGE.
*/
global virtual without sharing class HouseholdNaming {
    
    @future 
    global static void FutureUpdateNames(list<id> hhids){       
        //set our process control to avoid recursive updating on household records
        HouseholdProcessControl.inFutureContext = true;
        
        //create a new instance of the HouseholdNaming class -
        //and pass in our param
        //this is b/c of the global visibility
        HouseholdNaming hn = new HouseholdNaming();
        hn.UpdateNames(hhids);      
    }
    global virtual string getOtherName(list<Contact> ContactList){return null;}
    global virtual void UpdateNames(list<id> hhids){
        
        //we need this turned on to prevent recursive triggering on household creation        
        HouseholdProcessControl.inFutureContext = true;    
        
        //get all of the contacts and their associated households
        list<Contact> contactlist = [select id, Firstname, Salutation, LastName, Household__r.id, Naming_Exclusions__c from Contact where Household__c IN :hhids];
        list<Household__c> hhupdatelist = [select Name, SYSTEM_CUSTOM_NAMING__c from Household__c where id IN : hhids];
        
        map<id, list<Contact>> hhIDContactMap = new map<id, list<Contact>>();
        
        //sort contacts by household
        for (Contact c : contactlist){          
            if(!hhIDContactMap.containskey(c.Household__r.id)){
                hhIDContactMap.put(c.Household__r.id, new list<Contact>{c});
            }
            else{
                list<Contact> clist = hhIDContactMap.get(c.Household__r.id);
                clist.add(c);
                
            }
        }
                
        //loop through the households needing name changes, if necessary, make them.
        for (Household__c h : hhupdatelist){    
        
            string customnameparam = '';
            if (h.SYSTEM_CUSTOM_NAMING__c != null){
                customnameparam = h.SYSTEM_CUSTOM_NAMING__c;
            }            
            
            if (!customnameparam.contains('Name'))
                h.Name = getHHName(hhIDContactMap.get(h.id));   
            
            if (!customnameparam.contains('Formal_Greeting__c'))
                h.Formal_Greeting__c = getFormalName(hhIDContactMap.get(h.id));

            if (!customnameparam.contains('Informal_Greeting__c'))
                h.Informal_Greeting__c = getInformalName(hhIDContactMap.get(h.id));             
        } 
        
        if (!hhupdatelist.isEmpty()) Database.update(hhupdatelist, false);
    }
    
    //get generic household name 
    global virtual string getHHName(list<Contact> ContactList){
        string householdname = '';
        
        if(ContactList != null){
            //most of our records will be either one or two people per household, so we'll handle
            //those cases seperately, process medium size households, then
            //go on to larger
            
            set<string> contactlastnames = new set<string>(); 
             
            for (Contact c : ContactList){
                //add valid values to the contactnames and lastnames lists for processing
                if(c.Naming_Exclusions__c == null || !c.Naming_Exclusions__c.contains('Household__c.Name'))
                    contactlastnames.add(c.LastName);
            }
            
            //we lost our only contact - he/she's anonymous
            if (contactlastnames.isEmpty())
                householdname = 'Anonymous ' + system.label.DefaultHouseholdName;
                    
            //standard hh naming
            else if (contactlastnames.size() > 0 && contactlastnames.size() < 10){          
                integer x = 0;
                for (string lastname : contactlastnames){
                    x++;
                    householdname += lastname;
                    
                    if (x < contactlastnames.size() - 1)
                        householdname += ', ';
                    else if (x == contactlastnames.size() - 1)
                        householdname += ' and ';
                    else
                        householdname += ' ' + system.label.DefaultHouseholdName;                                
                }
            }
            
            //more than 10 DIFFERENT last names, default to family, it can be overwritten in this 
            //special case
            else{
               householdname = ContactList[0].LastName;
               householdname += ' and friends ' + system.label.DefaultHouseholdname;
            }
        }
        return householdname;
    }
    
    
    //just pile up first names for qualifying contacts
    global virtual string getInformalName(list<Contact> ContactList){
        string informalname = ''; 
       
        list<Contact> contactnames = new list<Contact>();
       
        integer i = 0;
        //remove any excluded contacts
        if (Contactlist != null){
        
        for (Contact c : ContactList){ 
            if(c.FirstName != null && (c.Naming_Exclusions__c == null || !c.Naming_Exclusions__c.contains('Household__c.Informal_Greeting__c')))
                contactnames.add(c);
        }
        
        integer listsize = 0;
        if (contactnames != null)
            listsize = contactnames.size();
        
        if (listsize == 0)
            informalname = 'Sir or Madame';
        
        else if (listsize == 1)
            informalname = contactnames[0].FirstName;
        
        else if (listsize == 2)
            informalname = contactnames[0].FirstName + ' and ' + contactnames[1].FirstName;
        
        //more than 2
        else{
            integer counter = 1;
            for (Contact c : contactnames){
                
                //we're at the first name in the group
                if (counter == 1)
                    informalname += c.FirstName;
                //if we're at the last name in the group
                else if (counter == contactnames.size())
                    informalname += ' and ' + c.FirstName;
                //we're somewhere in the middle of the group
                else
                    informalname += ', ' + c.Firstname;
                
                counter++;
            }  
        }
        }
        return informalname;
    }
    
    global virtual string getFormalName(list<Contact> ContactList){
        string formalname = ''; 
       
        list<Contact> contactnames = new list<Contact>();
       
        integer i = 0;
        
        if (Contactlist != null){
        //remove any excluded contacts
        for (Contact c : ContactList){ 
            if(c.Naming_Exclusions__c == null || !c.Naming_Exclusions__c.contains('Household__c.Formal_Greeting__c'))
                contactnames.add(c);
        }
        
            //chuck all of the last names in a set, lets see how many different ones we have
        set<string> lastnamelist = new set<string>();
        for (Contact c : contactnames)
            lastnamelist.add(c.LastName);  
      
      
        //no non-excluded contact for this household
        if (contactnames.size() == 0){                        
            formalname += '-'; 
        }
        else if (contactnames.size() > 0 && contactnames.size() < 9){
            
            integer counter = 1;
            for(Contact c : contactnames){    
                    
                if (c.Salutation != null)
                    formalname += c.Salutation + ' ';
                if (c.FirstName != null)
                    formalname += c.FirstName + ' ';                    
                if (lastnamelist.size() != 1 && counter != contactnames.size())
                    formalname += c.LastName;
                    
                //any first name before the last and next to last gets the
                //same treatment, a comma.
                if(counter < (contactnames.size() - 1))
                    formalname += ', ';
                else if (counter == (contactnames.size() - 1))
                    formalname += ' and ';                          
                //add the family name     
                else
                    formalname += c.LastName;
        
                counter++;
            }            
        }          
        
        //else we have 9 or more people
        else{        
            //some default name here        
            if (contactnames[0].Salutation != null)
                formalname += contactnames[0].Salutation + ' ';
            if (contactnames[0].FirstName != null)
                formalname += contactnames[0].FirstName + ' ';                    
            
            formalname += contactnames[0].LastName + ' and friends';
        }
        }
        return formalname;
    }
    
    //interface for the batch jobs on advanced greeting activation
    public void ActivateAdvancedGreetings(boolean isTest){
        
        
        //shamelessly borrowed from opprollups      
        integer batchSize = 200;
        
        //start the activation batch process, used only once, on initial click
        //to enable batch rollups
        BATCH_HouseholdNaming batch = new BATCH_HouseholdNaming('SELECT id,Household__r.id, ' + 
        'household__r.Name, Household__r.Informal_Greeting__c, Household__r.Formal_Greeting__c, ' +
        'household__r.SYSTEM_CUSTOM_NAMING__c, Naming_Exclusions__c,  Firstname, lastname, ' +
        'salutation ' +
        'FROM Contact where Household__c != null ' +
        (isTest ? ' and lastname=\'%LastName0%\' ' : '') + 
        'LIMIT 200', true);
        id batchProcessId = database.executeBatch(batch, batchSize);        
        
    }
    
    
    /*** Test Methods ***/
    static testMethod void HouseholdNamingTest(){
      //settings
      Households_Settings__c householdSettingsForTests = Households.getHouseholdsSettingsForTests(
                new Households_Settings__c (
                    Household_Rules__c = Households.ALL_PROCESSOR,
                    Always_Rollup_to_Primary_Contact__c = false,
                    Enable_Opp_Rollup_Triggers__c = true,
                    Excluded_Account_Opp_Rectypes__c = null,
                    Excluded_Account_Opp_Types__c = null,
                    Excluded_Contact_Opp_Rectypes__c = null,
                    Excluded_Contact_Opp_Types__c = null                    
                ));
                
            Contacts_and_Orgs_Settings__c contactSettingsForTests = Constants.getContactsSettingsForTests(new Contacts_and_Orgs_Settings__c (
                Account_Processor__c = Constants.ONE_TO_ONE_PROCESSOR,
                Enable_Opportunity_Contact_Role_Trigger__c = true,
                Opportunity_Contact_Role_Default_role__c = 'Donor'
            ));
        
        integer i;
        
        list<Contact> insertlist = new list<Contact>();
        for (i = 0; i < 20; i++){
            Contact c = new Contact(LastName = 'LastName' + i, FirstName = 'FirstName' + i, Salutation = 'Mr.');
            if (i == 1){
                c.Naming_Exclusions__c = 'Household Name';
            }
            
            insertlist.add(c);
        }       
                
        insert insertlist;
        list<id> householdids = new list<id>();
        
        list<Contact> clist = [select Naming_Exclusions__c, Household__r.id, Household__r.Name, Household__r.Informal_Greeting__c, Household__r.Formal_Greeting__c from Contact where id IN :insertlist limit 2000];
        
        for (Contact c : clist){
            if (c.household__r.id != null)
              householdids.add(c.Household__r.id);          
        }
        system.assert(householdids.size() > 0);
        
        //use future to prevent the before HH trigger from firing    
        test.starttest();         
        Householdnaming.FutureUpdateNames(householdids);
        test.stoptest();
        //test a single member household, lastname, firstname, householdname:
        Contact con = [select Household__r.Name, Household__r.Informal_Greeting__c, Household__r.Formal_Greeting__c from Contact where FirstName = 'FirstName0'];
        system.assertEquals('LastName0 ' +  system.label.DefaultHouseholdName, con.Household__r.Name);  
        system.assertEquals('FirstName0',con.Household__r.Informal_Greeting__c);
        system.assertEquals('Mr. FirstName0 LastName0', con.Household__r.Formal_Greeting__c);
        
        Householdnaming hn = new Householdnaming();
        //test for coverage
         hn.ActivateAdvancedGreetings(true);
        
    }
    
    static testMethod void HouseholdNamingUpdateTest(){
        //settings
      Households_Settings__c householdSettingsForTests = Households.getHouseholdsSettingsForTests(
                new Households_Settings__c (
                    Household_Rules__c = Households.ALL_PROCESSOR,
                    Always_Rollup_to_Primary_Contact__c = false,
                    Enable_Opp_Rollup_Triggers__c = true,
                    Excluded_Account_Opp_Rectypes__c = null,
                    Excluded_Account_Opp_Types__c = null,
                    Excluded_Contact_Opp_Rectypes__c = null,
                    Excluded_Contact_Opp_Types__c = null,     
                    Advanced_Household_Naming__c = true               
                ));
                
            Contacts_and_Orgs_Settings__c contactSettingsForTests = Constants.getContactsSettingsForTests(new Contacts_and_Orgs_Settings__c (
                Account_Processor__c = Constants.ONE_TO_ONE_PROCESSOR,
                Enable_Opportunity_Contact_Role_Trigger__c = true,
                Opportunity_Contact_Role_Default_role__c = 'Donor'
            ));
        
        integer i;
        
        list<Contact> insertlist = new list<Contact>();
        for (i = 0; i < 200; i++){
            Contact c = new Contact(LastName = 'LastName' + i, FirstName = 'FirstName' + i, Salutation = 'Mr.');
            if (i == 1){
                c.Naming_Exclusions__c = 'Household Name';
            }
            
            insertlist.add(c);
        }  
        
        //turn off the before trigger via our processor:
        HouseholdProcessControl.inFutureContext = true;        
        insert insertlist;
        list<id> householdids = new list<id>();
        
        list<Contact> clist = [select Naming_Exclusions__c, Household__r.id, Household__r.Name, Household__r.Informal_Greeting__c, Household__r.Formal_Greeting__c from Contact where id IN :insertlist limit 2000];
        
        for (Contact c : clist){
            if (c.household__r.id != null)
              householdids.add(c.Household__r.id);          
        }
        
        //update some contacts and note the changes
        //set first one to the last household, next two to the second, next five to the third, 
        //and next 10 to the last (array spots end, end - 1, etc.)
        integer counter = 0;
        for (Contact c : clist){
            if(counter == 0)
                c.Household__c = clist[clist.size() - 1].Household__c;
            else if (counter > 0 && counter < 3)
                c.Household__c = clist[clist.size() - 2].Household__c;
            else if (counter > 2 && counter < 8)
                c.Household__c = clist[clist.size() - 3].Household__c;
            else if (counter < 20)
                c.Household__c = clist[clist.size() - 4].Household__c;
            counter++;
        }
        //this should cause renaming to happen
        //but not trigger the before household_update, since 
        //since it will be in the same transaction                 
        update clist;
        
        list<id> newhhids = new list<id>();
        newhhids.add(clist[clist.size() - 1].Household__r.id);
        newhhids.add(clist[clist.size() - 2].Household__r.id);
        newhhids.add(clist[clist.size() - 3].Household__r.id);
        newhhids.add(clist[clist.size() - 4].Household__r.id);
        
        //update our newly updated contact's households
        Householdnaming hn = new Householdnaming();
        test.startTest();
        hn.UpdateNames(newhhids);
        
        //take teh list of contacts in each of the households in newhhids, and run them
        //back through the global string calls to test them - this never quite worked -
        // i think something about how HHs are created, with the contact re-update is causing
        //an issue in testing
       /* 
        map<id, Household__c> hhmap = new map<id, Household__c>([select SYSTEM_CUSTOM_NAMING__c, Name, Informal_greeting__c, Formal_greeting__c from Household__c where id IN :newhhids]);
        
        list<Contact> lc = new list<contact>([select household__r.id, 
        household__r.name, household__r.Informal_Greeting__c, FirstName, Salutation, LastName, 
        Naming_Exclusions__c, household__r.SYSTEM_CUSTOM_NAMING__c,
        household__r.Formal_Greeting__c from Contact where household__r.id IN :newhhids]);
        
        map<id,list <Contact>> hhidContactMap = new map<id, list <Contact>>();
        for (Contact c : lc){
            if(!hhIDContactMap.containskey(c.Household__r.id)){
                hhIDContactMap.put(c.Household__r.id, new list<Contact>{c});
            }
            else{
                list<Contact> conlist = hhIDContactMap.get(c.Household__r.id);
                conlist.add(c);                
            }
        }
        
        //for each one, run its contact through the  test methods
        //and compare it with the household values from teh first contact
        //record              
        for (id HHID : hhidContactMap.keySet()){
            if (hhidcontactmap.get(hhid)[0].household__r.SYSTEM_CUSTOM_NAMING__c != null){
                system.assert(!hhidcontactmap.get(hhid)[0].household__r.SYSTEM_CUSTOM_NAMING__c.contains('Name'));
                system.assert(!hhidcontactmap.get(hhid)[0].household__r.SYSTEM_CUSTOM_NAMING__c.contains('Informal_Greeting__c'));
                system.assert(!hhidcontactmap.get(hhid)[0].household__r.SYSTEM_CUSTOM_NAMING__c.contains('Formal_Greeting__c'));
            } 
            system.assertEquals(hhMap.get(hhid).Name, hn.getHHName(hhidcontactmap.get(hhid)));
            system.assertEquals(hhidcontactmap.get(hhid)[0].Household__r.Informal_Greeting__c, hn.getInformalName(hhidcontactmap.get(hhid)));
            system.assertEquals(hhidcontactmap.get(hhid)[0].Household__r.Formal_Greeting__c, hn.getFormalName(hhidcontactmap.get(hhid)));
        }       
        */
    test.stopTest();
        
        
    }  
}